package cn.krui.helper.db;

import cn.krui.helper.ConfigurationBean;
import cn.krui.helper.tools.MapModelUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PreDestroy;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;

/**
 * @author zc
 */
public class DBHelper {
  static SqlSessionFactory factory;
  
  Boolean dirty;
  
  boolean autoCommit;
  
  boolean cacheEnabled;
  
  PageModel pageModel;
  
  Configuration cfg;
  
  Executor m_Executor;
  
  SQL.SelectSQL lastSelectSQL;
  
  SQL.InsertSQL lastInsertSQL;
  
  SQL.UpdateSQL lastUpdateSQL;
  
  SQL.DeleteSQL lastDeleteSQL;
  
  SQL.ExecuteSQL lastExecuteSQL;
  
  public DBHelper(SqlSessionFactory factory, PageModel pageModel, boolean autoCommit, boolean cacheEnabled) {
    this.m_Executor = null;
    this.lastSelectSQL = null;
    this.dirty = Boolean.valueOf(false);
    if (pageModel != null) {
      this.pageModel = pageModel;
    } else {
      this.pageModel = ConfigurationBean.pageModel;
    } 
    if (factory != null) {
      DBHelper.factory = factory;
    } else {
      DBHelper.factory = ConfigurationBean.factory;
    } 
    this.autoCommit = autoCommit;
    this.cacheEnabled = cacheEnabled;
    this.cfg = DBHelper.factory.getConfiguration();
  }
  
  synchronized Executor executor() {
    if (this.m_Executor == null || this.m_Executor.isClosed()) {
      Environment environment = this.cfg.getEnvironment();
      TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), null, this.autoCommit);
      this.m_Executor = this.cfg.newExecutor(tx, this.cfg.getDefaultExecutorType());
    } 
    return this.m_Executor;
  }
  
  public DBHelper(boolean autoCommit, boolean cacheEnabled) {
    this(null, null, autoCommit, cacheEnabled);
  }
  
  public DBHelper() {
    this(true, false);
  }
  
  public synchronized SQL.SelectSQL getLastSelectSQL() {
    return this.lastSelectSQL;
  }
  
  public synchronized Map<String, Object> selectOne(SQL.SelectSQL sql) {
    List<Map<String, Object>> r = select(sql);
    if (r.isEmpty()){
      return new HashMap<>(); 
    }
    return r.get(0);
  }
  
  public synchronized <T> T selectOne(SQL.SelectSQL sql, Class<?> entityType, ResultMapping... mappings) {
    List<T> r = select(sql, entityType, mappings);
    if (r.isEmpty()){
      return null;
    }
    return r.get(0);
  }
  
  public synchronized Map<String, Object> selectOne(String columns, String table, String where, Map<String, Object> params) {
    return selectOne(SQL.select(new String[] { columns }).from(new String[] { table }).where(where).addParameters(params));
  }
  
  public synchronized <T> T selectOne(String columns, String table, String where, Map<String, Object> params, Class<?> entityType, ResultMapping... mappings) {
    return selectOne(SQL.select(new String[] { columns } ).from(new String[] { table } ).where(where).addParameters(params), entityType, mappings);
  }
  
  public synchronized List<Map<String, Object>> select(SQL.SelectSQL sql) {
    return select(sql, null);
  }
  
  public synchronized <T> List<T> select(SQL.SelectSQL sql, Class<?> entityType, ResultMapping... mappings) {
    return select(sql, (Map<String, Object>)null, entityType, mappings);
  }
  
  public synchronized List<Map<String, Object>> select(String columns, String table, String where, Map<String, Object> params) {
    return select(SQL.select(new String[] { columns }).from(new String[] { table }).where(where).addParameters(params));
  }
  
  public synchronized <T> List<T> select(String columns, String table, String where, Map<String, Object> params, Class<?> entityType, ResultMapping... mappings) {
    return select(SQL.select(new String[] { columns } ).from(new String[] { table } ).where(where).addParameters(params), entityType, mappings);
  }
  
  public synchronized DataPage<Map<String, Object>> select(String columns, String table, String where, String orderBy, Map<String, Object> params, int pageNumber, int pageSize) {
    SQL.SelectSQL sql = SQL.select(new String[] { "COUNT(*) as total_count" }).from(new String[] { table }).where(where).addParameters(params);
    Long rowCount = (Long)selectOne(sql).get("total_count");
    sql.fields.clear();
    sql.select(new String[] { columns }).page(pageNumber, pageSize).orderBy(new String[] { orderBy });
    List<Map<String, Object>> data = select(sql);
    return new DataPage<>(data, rowCount, Integer.valueOf(pageNumber), Integer.valueOf(pageSize));
  }
  
  public synchronized <T> DataPage<T> select(String columns, String table, String where, String orderBy, Map<String, Object> params, int pageNumber, int pageSize, Class<?> entityType, ResultMapping... mappings) {
    SQL.SelectSQL sql = SQL.select(new String[] { "COUNT(*) as total_count" }).from(new String[] { table }).where(where).addParameters(params);
    Long rowCount = (Long)selectOne(sql).get("total_count");
    sql.fields.clear();
    sql.select(new String[] { columns }).page(pageNumber, pageSize).orderBy(new String[] { orderBy });
    List<T> data = select(sql, entityType, mappings);
    return new DataPage<>(data, rowCount, Integer.valueOf(pageNumber), Integer.valueOf(pageSize));
  }
  
  public synchronized List<Map<String, Object>> select(String columns, String table, String where, String orderBy, Map<String, Object> params) {
    return select(SQL.select(new String[] { columns }).from(new String[] { table }).where(where).orderBy(new String[] { orderBy }).addParameters(params));
  }
  
  public synchronized <T> List<T> select(String columns, String table, String where, String orderBy, Map<String, Object> params, Class<?> entityType, ResultMapping... mappings) {
    return select(SQL.select(new String[] { columns } ).from(new String[] { table }).where(where).orderBy(new String[] { orderBy } ).addParameters(params), entityType, 
        mappings);
  }
  
  public synchronized List<Map<String, Object>> select(String columns, String table, String where, String orderBy, Map<String, Object> params, int top) {
    return select(SQL.select(new String[] { columns }).from(new String[] { table }).where(where).orderBy(new String[] { orderBy }).addParameters(params).rows(Integer.valueOf(top)));
  }
  
  public synchronized <T> List<T> select(String columns, String table, String where, String orderBy, Map<String, Object> params, int top, Class<?> entityType, ResultMapping... mappings) {
    return select(SQL.select(new String[] { columns }).from(new String[] { table } ).where(where).orderBy(new String[] { orderBy }).addParameters(params).rows(Integer.valueOf(top)), 
        entityType, mappings);
  }
  
  List<ResultMap> builderResultMap() {
    List<ResultMap> mapList = new ArrayList<>();
    ResultMap map = (new ResultMap.Builder(this.cfg, "select-Inline", LinkedHashMap.class, new ArrayList()))
      .build();
    mapList.add(map);
    return mapList;
  }
  
  ResultMap builderResultMap(Class<?> type, Boolean autoMapping, ResultMapping... mapping) {
    String id = type.getName();
    if (this.cfg.hasResultMap(id)){
      return this.cfg.getResultMap(id); 
    }
    List<ResultMapping> rm = new ArrayList<>();
    boolean hasPrimaryKey = false;
    if (mapping != null) {
      byte b;
      int i;
      ResultMapping[] arrayOfResultMapping;
      for (i = (arrayOfResultMapping = mapping).length, b = 0; b < i; ) {
        ResultMapping m = arrayOfResultMapping[b];
        if (!hasPrimaryKey && m.getFlags().contains(ResultFlag.ID)){
          hasPrimaryKey = true; 
        }
        rm.add(m);
        b++;
      } 
    } 
    if (!hasPrimaryKey){
      try {
        Field field = type.getDeclaredField("id");
        rm.add(builderMapping("id", "id", field.getType(), true));
      } catch (NoSuchFieldException|SecurityException noSuchFieldException) {} 
    }
    ResultMap map = (new ResultMap.Builder(this.cfg, type.getName(), type, rm, autoMapping)).build();
    this.cfg.addResultMap(map);
    return map;
  }
  
  public synchronized List<Map<String, Object>> select(SQL.SelectSQL sql, Map<String, Object> params) {
    if (sql.pageModel == null){
      sql.pageModel = this.pageModel; 
    }
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.cfg);
    SqlSource source = sqlSourceParser.parse(sql.toString(), Map.class, null);
    // MappedStatement statement = (new MappedStatement.Builder(this.cfg, "select-8d4a64b7-d674-11e8-88d3-bcaec57057d6", 
    MappedStatement statement = (new MappedStatement.Builder(this.cfg, List.class.getName(), 
        source, SqlCommandType.SELECT)).resultMaps(builderResultMap()).flushCacheRequired(!this.cacheEnabled).build();
    try {
      this.lastSelectSQL = sql;
      Map<String, Object> parameter = sql.getParameters(true);
      List<Map<String, Object>> result = executor().query(statement, parameter, RowBounds.DEFAULT, 
          Executor.NO_RESULT_HANDLER);
      if (sql.inverse.booleanValue()) {
        List<Map<String, Object>> buffer = new ArrayList<>();
        for (int i = result.size() - 1; i > -1; i--){
          buffer.add(result.get(i)); 
        }
        return buffer;
      } 
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      if (this.autoCommit){
        close(); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized void select(SQL.SelectSQL sql, Map<String, Object> params, ResultHandler<Map<String, Object>> handler) {
    if (sql.pageModel == null){
      sql.pageModel = this.pageModel; 
    }
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.cfg);
    SqlSource source = sqlSourceParser.parse(sql.toString(), Map.class, null);
    // MappedStatement statement = (new MappedStatement.Builder(this.cfg, "select-8d4a64b7-d674-11e8-88d3-bcaec57057d6", 
    MappedStatement statement = (new MappedStatement.Builder(this.cfg, Map.class.getName(),
        source, SqlCommandType.SELECT)).resultMaps(builderResultMap()).flushCacheRequired(!this.cacheEnabled).build();
    try {
      this.lastSelectSQL = sql;
      Map<String, Object> parameter = sql.getParameters(true);
      final Cursor<Map<String, Object>> cursor = executor().queryCursor(statement, parameter, RowBounds.DEFAULT);
      for (Map<String, Object> row : cursor) {
        handler.handleResult(new ResultContext<Map<String, Object>>() {
              public Map<String, Object> getResultObject() {
                return row;
              }
              
              public int getResultCount() {
                return cursor.getCurrentIndex();
              }
              
              public boolean isStopped() {
                return false;
              }
              
              public void stop() {
                try {
                  cursor.close();
                } catch (IOException iOException) {}
              }
            });
      } 
      handler.handleResult(new ResultContext<Map<String, Object>>() {
            public Map<String, Object> getResultObject() {
              return null;
            }
            
            public int getResultCount() {
              return -1;
            }
            
            public boolean isStopped() {
              return true;
            }
            
            public void stop() {}
          });
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      if (this.autoCommit){
        close(); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized ResultMapping builderMapping(String property, String column, Class<?> javaType) {
    return (new ResultMapping.Builder(this.cfg, property, column, javaType)).build();
  }
  
  public synchronized ResultMapping builderMapping(String property, String column, Class<?> javaType, boolean isPrimaryKey) {
    List<ResultFlag> flags = new ArrayList<>();
    if (isPrimaryKey){
      flags.add(ResultFlag.ID); 
    }
    return (new ResultMapping.Builder(this.cfg, property, column, javaType)).flags(flags).build();
  }
  
  public synchronized ResultMapping builderMapping(String property, Class<?> ofType, String columnPrefix, Boolean autoMapping, ResultMapping... mapping) {
    builderResultMap(ofType, autoMapping, mapping);
    return (new ResultMapping.Builder(this.cfg, property)).nestedResultMapId(ofType.getName()).columnPrefix(columnPrefix)
      .build();
  }
  
  public synchronized <T> List<T> select(SQL.SelectSQL sql, Map<String, Object> params, Class<?> entityType, ResultMapping... mapping) {
    return select(sql, params, entityType, (Boolean)null, mapping);
  }
  
  public synchronized <T> List<T> select(SQL.SelectSQL sql, Map<String, Object> params, Class<?> entityType, Boolean autoMapping, ResultMapping... mapping) {
    List<ResultMap> map = new ArrayList<>();
    map.add(builderResultMap(entityType, autoMapping, mapping));
    return select(sql, params, entityType, map);
  }
  
  public synchronized <T> List<T> select(SQL.SelectSQL sql, Map<String, Object> params, Class<?> entityType, List<ResultMap> resultMaps) {
    if (sql.pageModel == null){
      sql.pageModel = this.pageModel; 
    }
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.cfg);
    SqlSource source = sqlSourceParser.parse(sql.toString(), Map.class, null);
    // MappedStatement statement = (new MappedStatement.Builder(this.cfg, "select-obj-8d4a64b7-d674-11e8-88d3-bcaec57057d6", 
    MappedStatement statement = (new MappedStatement.Builder(this.cfg, entityType.getName(), 
        source, SqlCommandType.SELECT)).resultMaps(resultMaps).flushCacheRequired(!this.cacheEnabled).build();
    try {
      this.lastSelectSQL = sql;
      sql.addParameters(params);
      Map<String, Object> parameter = sql.getParameters(true);
      List<T> result = executor().query(statement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
      if (sql.inverse.booleanValue()) {
        List<T> buffer = new ArrayList<>();
        for (int i = result.size() - 1; i > -1; i--){
          buffer.add(result.get(i)); 
        }
        return buffer;
      } 
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      if (this.autoCommit){
        close(); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized Object insert(SQL.InsertSQL sql) {
    return insert(sql, (Map<String, Object>)null);
  }
  
  public synchronized SQL.InsertSQL getLastInsertSQL() {
    return this.lastInsertSQL;
  }
  
  public synchronized Object insert(SQL.InsertSQL sql, Map<String, Object> params) {
    try {
      if (ConfigurationBean.isOracle && sql.values.size() > 1){
        sql.useInsertAll = Boolean.valueOf(true); 
      }
      this.dirty = Boolean.valueOf(true);
      this.lastInsertSQL = sql;
      SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.cfg);
      SqlSource source = sqlSourceParser.parse(sql.toString(), Map.class, null);
      String idColumns = sql.getIdFields();
      // MappedStatement.Builder builder = new MappedStatement.Builder(this.cfg, "insert-8d4a64b7-d674-11e8-88d3-bcaec57057d6", source, 
      MappedStatement.Builder builder = new MappedStatement.Builder(this.cfg, Map.class.getName(), source, 
          SqlCommandType.INSERT);
      if ((!ConfigurationBean.isOracle || sql.values.size() == 1) && !idColumns.isEmpty()) {
        builder.keyColumn(idColumns).keyProperty(idColumns).keyGenerator((KeyGenerator)Jdbc3KeyGenerator.INSTANCE)
          .flushCacheRequired(!this.cacheEnabled);
      } else {
        builder.flushCacheRequired(!this.cacheEnabled);
      } 
      MappedStatement statement = builder.build();
      Map<String, Object> parameter = sql.getParameters(true);
      if (params != null){
        parameter.putAll(params); 
      }
      int c = executor().update(statement, parameter);
      String[] keys = statement.getKeyProperties();
      if (keys == null || keys.length == 0){
        return Integer.valueOf(c); 
      }
      if (keys.length == 1){
        return parameter.get(keys[0]); 
      }
      Map<String, Object> keyValues = new HashMap<>();
      byte b;
      int i;
      String[] arrayOfString1;
      for (i = (arrayOfString1 = keys).length, b = 0; b < i; ) {
        String k = arrayOfString1[b];
        keyValues.put(k, parameter.get(k));
        b++;
      } 
      return keyValues;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      if (this.autoCommit){
        close(); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized Object insert(String table, Map<String, Object> values) {
    return insert(SQL.insert(table).addValue(values));
  }
  
  public synchronized Map<String, Object> fixToSQLUNC(Map<String, Object> data) {
    LinkedHashMap<String, Object> result = new LinkedHashMap<>(data.size());
    for (Map.Entry<String, Object> item : data.entrySet()){
      result.put(propertyName2ColumnName(item.getKey()), item.getValue()); 
    }
    return result;
  }
  
  public synchronized String propertyName2ColumnName(String propertyName) {
    StringBuilder buffer = new StringBuilder();
    if (propertyName != null){
      for (int i = 0; i < propertyName.length(); i++) {
        char c = propertyName.charAt(i);
        if (i > 0 && Character.isUpperCase(c)){
          buffer.append('_'); 
        }
        buffer.append(Character.toLowerCase(c));
      }  
    }
    return buffer.toString();
  }
  
  <T> Map<String, Object> resolverEntity(T entity, Class<?> entityType) {
    Map<String, Object> fields = new HashMap<>();
    byte b;
    int i;
    Method[] arrayOfMethod;
    for (i = (arrayOfMethod = entityType.getMethods()).length, b = 0; b < i; ) {
      Method method = arrayOfMethod[b];
      String name = method.getName();
      if ((!name.startsWith("get") && !name.startsWith("is")) || method.getParameterCount() != 0 || 
        name.equals("getClass")) {
          b++;
    	  continue; 
      }
        
      if (name.startsWith("is")) {
        if (method.getReturnType() != Boolean.class && method.getReturnType() != boolean.class) {
            b++;
            continue; 
        }
        name = name.substring(2);
      } else {
        name = name.substring(3);
      } 
      if (name.length() != 0) {
        String fieldName = String.valueOf(name.substring(0, 1).toLowerCase()) + name.substring(1);
        ParameterInfo info = method.<ParameterInfo>getAnnotation(ParameterInfo.class);
        if (info == null){
          try {
            Field field = entityType.getDeclaredField(fieldName);
            info = field.<ParameterInfo>getAnnotation(ParameterInfo.class);
          } catch (NoSuchFieldException|SecurityException noSuchFieldException) {} 
        }
        if (info == null || info.exists()) {
          String columnName = propertyName2ColumnName(name);
          Object val = null;
          try {
            val = method.invoke(entity, new Object[0]);
          } catch (IllegalAccessException|IllegalArgumentException|java.lang.reflect.InvocationTargetException illegalAccessException) {}
          if (info != null) {
            if (val != null || info.ignoreNull()){
              fields.put((info.column().length() == 0) ? columnName : info.column(), 
                  new Parameter(fieldName, val, DirectionModel.IN, info.jdbcType(), info.javaType(), 
                    Integer.valueOf(info.numericScale()), info.typeHandler())); 
            }
          } else if (val != null) {
            fields.put(columnName, val);
          } 
        } 
      } 
      b++;
      continue;
    } 
    return fields;
  }
  
  public synchronized <T> Object insert(String table, T entity, Class<?> entityType) {
    SQL.InsertSQL sql = SQL.insert(table);
    Map<String, Object> p = resolverEntity(entity, entityType);
    if (p.containsKey("id")) {
      Object id = p.get("id");
      if (id == null || id.toString().equals("0")){
        p.remove("id"); 
      }
    } 
    sql.addValue(p);
    return insert(sql);
  }
  
  public synchronized SQL.UpdateSQL getLastUpdateSQL() {
    return this.lastUpdateSQL;
  }
  
  public synchronized int update(SQL.UpdateSQL sql) {
    try {
      this.dirty = Boolean.valueOf(true);
      this.lastUpdateSQL = sql;
      SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.cfg);
      SqlSource source = sqlSourceParser.parse(sql.toString(), Map.class, null);
      String idFields = sql.getIdFields();
      // MappedStatement statement = (new MappedStatement.Builder(this.cfg, "update-8d4a64b7-d674-11e8-88d3-bcaec57057d6", 
      MappedStatement statement = (new MappedStatement.Builder(this.cfg, Map.class.getName(), 
          source, SqlCommandType.UPDATE)).keyColumn(idFields).keyProperty(idFields)
        .flushCacheRequired(!this.cacheEnabled).build();
      Map<String, Object> parameter = sql.getParameters(true);
      int r = executor().update(statement, parameter);
      return r;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      if (this.autoCommit){
        close(); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized int update(String table, Map<String, Object> fields, String where, Map<String, Object> whereParams) {
    return update(SQL.update(table).addFields(fields).where(where).addParameters(whereParams));
  }
  
  public Map<String, Object> getNotNullView(Map<String, Object> data) {
    Map<String, Object> result = new LinkedHashMap<>();
    for (Map.Entry<String, Object> item : data.entrySet()) {
      if (item.getValue() != null){
        result.put(item.getKey(), item.getValue()); 
      }
    } 
    return result;
  }
  
  public synchronized <T> int update(String table, T entity, Class<?> entityType, String where) {
    Map<String, Object> params = (new MapModelUtils()).add(entity).getValues();
    Map<String, Object> fields = resolverEntity(entity, entityType);
    fields.remove("id");
    return update(SQL.update(table).addFields(fields).where(where).addParameters(params));
  }
  
  public synchronized SQL.DeleteSQL getLastDeleteSQL() {
    return this.lastDeleteSQL;
  }
  
  public synchronized int delete(SQL.DeleteSQL sql) {
    try {
      this.dirty = Boolean.valueOf(true);
      this.lastDeleteSQL = sql;
      SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.cfg);
      SqlSource source = sqlSourceParser.parse(sql.toString(), Map.class, null);
      // MappedStatement statement = (new MappedStatement.Builder(this.cfg, "delete-8d4a64b7-d674-11e8-88d3-bcaec57057d6", 
      MappedStatement statement = (new MappedStatement.Builder(this.cfg, Map.class.getName(), 
          source, SqlCommandType.DELETE)).flushCacheRequired(!this.cacheEnabled).build();
      Map<String, Object> parameter = sql.getParameters(true);
      int r = executor().update(statement, parameter);
      return r;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      if (this.autoCommit){
        close(); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized int delete(String table, String where, Map<String, Object> params) {
    return delete(SQL.delete(table).where(where).addParameters(params));
  }
  
  public synchronized SQL.ExecuteSQL getLastExecuteSQL() {
    return this.lastExecuteSQL;
  }
  
  public synchronized List<Map<String, Object>> exec(SQL.ExecuteSQL sql) {
    this.lastExecuteSQL = sql;
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.cfg);
    SqlSource source = sqlSourceParser.parse(sql.toString(), Map.class, null);
    // MappedStatement statement = (new MappedStatement.Builder(this.cfg, "exec-8d4a64b7-d674-11e8-88d3-bcaec57057d6", 
    MappedStatement statement = (new MappedStatement.Builder(this.cfg, Map.class.getName(), 
        source, SqlCommandType.UNKNOWN)).resultMaps(builderResultMap()).statementType(StatementType.CALLABLE)
      .flushCacheRequired(!this.cacheEnabled).build();
    try {
      Map<String, Object> parameter = sql.getParameters(true);
      List<Map<String, Object>> r = executor().query(statement, parameter, RowBounds.DEFAULT, 
          Executor.NO_RESULT_HANDLER);
      return r;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      if (this.autoCommit){
        close(); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized List<Map<String, Object>> exec(String procName, Map<String, Object> params) {
    return exec(SQL.exec(procName).addParameters(params));
  }
  
  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null){
      return (TransactionFactory)new ManagedTransactionFactory(); 
    }
    return environment.getTransactionFactory();
  }
  
  public Configuration getConfiguration() {
    return this.cfg;
  }
  
  protected void finalize() throws Throwable {
    close();
    super.finalize();
  }
  
  @PreDestroy
  public synchronized void close() {
    try {
      if (this.m_Executor != null) {
        this.m_Executor.getTransaction().close();
        this.m_Executor.close(isCommitOrRollbackRequired(false));
      } 
      this.dirty = Boolean.valueOf(false);
      this.m_Executor = null;
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized void rollback(boolean force) {
    try {
      if (this.m_Executor != null) {
        this.m_Executor.rollback(isCommitOrRollbackRequired(force));
        this.m_Executor.getTransaction().close();
      } 
      this.dirty = Boolean.valueOf(false);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      if (this.m_Executor != null){
        this.m_Executor.close(isCommitOrRollbackRequired(false)); 
      }
      ErrorContext.instance().reset();
    } 
  }
  
  public synchronized void rollback() {
    rollback(false);
  }
  
  public synchronized void commit() {
    commit(false);
  }
  
  private boolean isCommitOrRollbackRequired(boolean force) {
    return !((this.autoCommit || !this.dirty.booleanValue()) && !force);
  }
  
  public synchronized void clearCache() {
    if (this.m_Executor != null){
      this.m_Executor.clearLocalCache(); 
    }
  }
  
  public synchronized Connection getConnection() {
    try {
      return executor().getTransaction().getConnection();
    } catch (SQLException e) {
      throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
    } 
  }
  
  public synchronized void commit(boolean force) {
    try {
      if (this.m_Executor != null) {
        this.m_Executor.commit(isCommitOrRollbackRequired(force));
        this.m_Executor.getTransaction().close();
        this.dirty = Boolean.valueOf(false);
      } 
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      if (this.m_Executor != null){
        this.m_Executor.close(isCommitOrRollbackRequired(false)); 
      }
      ErrorContext.instance().reset();
    } 
  }
}
